local env


----------------------------------------------------------------------------------------------
if CooldownWatch.modules.InitConfig.status=='load' then
function CooldownWatch.modules.InitConfig.init(...)
	local frame, frameByName, inventoryframe, frameBySlot, seconds, minutes = ...
	local db = CooldownWatchVar
	----------------------------------------------------------------------------
	-- StatusBar class
	--  Same interface as the Blizzard StatusBar, but grows from right to left.
	----------------------------------------------------------------------------
	local CreateStatusBar
	do
		local prototype = {}
		
		function prototype:SetStatusBarTexture(...)
			self.texture:SetTexture(...)
		end
		
		function prototype:GetStatusBarTexture(...)
			return self.texture
		end

		function prototype:SetStatusBarColor(...)
			self.texture:SetVertexColor(...)
		end

		function prototype:GetStatusBarColor(...)
			return self.texture:GetVertexColor(...)
		end

		local function Update(self)
			local t = self.texture
			local x = (self.max-self.value)/self.diff
			if x>1 then 
				x=1 
			elseif x<0 then 
				x=0 
			end
			local width = self:GetWidth()
			local wx = width*x
			t:SetPoint("RIGHT",self,"RIGHT",wx,0)
			t:SetPoint("LEFT",self,"LEFT",wx,0)
			t:SetTexCoord(x, 1, 0, 1)
		end

		function prototype:SetValue(v)
			self.value = v
			Update(self)
		end

		function prototype:SetMinMaxValues(a,b)
			b = b>a and b or a+1
			self.min = a
			self.max = b
			self.diff = b-a
			Update(self)
		end

		function CreateStatusBar(...)
			local name = ...
			local statusbar = CreateFrame("Frame", ...)

			local t = statusbar:CreateTexture(name.."Texture","BORDER")
			t:SetTexCoordModifiesRect(true)
			t:SetPoint("TOP",statusbar,"TOP")
			t:SetPoint("BOTTOM",statusbar,"BOTTOM")
			t:SetWidth(statusbar:GetWidth())

			statusbar.texture = t
			statusbar.diff = 1
			statusbar.min = 0
			statusbar.max = 1
			statusbar.value = 1

			for k,v in pairs(prototype) do
				statusbar[k] = v
			end

			Update(statusbar)

			return statusbar
		end
	end

	-- Finds the spell index in the spell book.
	local function GetSpellIndex(spellname, spelltype)
		local index = 1
		while true do
			local name = GetSpellName(index, spelltype)
			if not name then return end
			if name==spellname then return index end
			index = index+1
		end
	end


	local function GetItemTexture(name) 
		return select(10,GetItemInfo(name)) 
	end
	function CooldownWatch:CreateWatchFrame(watchtype, watchname, options)
		if watchtype~='debug' and frameByName[watchname] then
			self:Print(watchname..' already exists')
			return
		end

		options = options or {}
		
		local name = watchname
		self:Trace("CreateWatchFrame", watchtype, name)
		local GetCooldown, id, idtype, GetTexture
		-- The cooldown frames are going to call GetCooldown(id, idtype) and GetTexture(id, idtype).
		-- TODO:
		-- GetPetActionCooldown(index)
		-- GetActionCooldown(slot)
		-- GetContainerItemCooldown(bagID, slot)

		local notfound
		if watchtype == "spell" or watchtype == "petspell" then
			if name:match(".+%(.+%)") then
				id = name..'()' -- the braces are required for Faerie Fire (Feral).
			else
				id = name
			end
			-- Check if the spell is currently in the spell book.
			if not GetSpellIndex(name, watchtype == "spell" and BOOKTYPE_SPELL or BOOKTYPE_PET) then
				notfound = true
			end
			if not id then
				self:Printf("spell not found, name=%s, watchtype=%", name, watchtype)
				return
			end
			GetCooldown = GetSpellCooldown
			GetTexture = GetSpellTexture
		elseif watchtype == "debug" then
			id, idtype = name, nil
			if not id then
				self:Printf("spell not found, name=%s, idtype=%", name, idtype)
				return
			end
			local duration = options.duration
			local remaining = options.remaining or duration
			local offset = remaining-duration
			GetCooldown = function()
				return GetTime()+offset, duration, 1
			end
			GetTexture = GetSpellTexture
		elseif watchtype == "equip" then
			-- GetInventoryItemCooldown("unit", slotId)
			if type(name) == "string" then
				if not name:lower():find("slot$") then
					name = name.."slot"
				end
				idtype = GetInventorySlotInfo(name)
			else
				idtype = name
			end
			id = "player"
			GetCooldown = GetInventoryItemCooldown
			GetTexture = GetInventoryItemTexture
		elseif watchtype == "item" then
			-- GetItemCooldown(itemID | "name" | "item link")
			id = name
			idtype = nil
			GetCooldown = GetItemCooldown
			GetTexture = GetItemTexture
		end
		local frameid = (#frame) + 1

		local framename = "CooldownWatchFrame"..frameid
		if not self.CooldownParent then
			local p = CreateFrame("Frame", "CooldownWatchParent", UIParent)
			p:SetScript("OnHide", function()
				if self.configmode then return end
				for i,f in ipairs(frame) do
					f.Stop()
				end
			end)
			p:SetScript("OnShow", function()
				if self.configmode then return end
				for i,f in ipairs(frame) do
					f.CheckCooldown()
				end
			end)
			self.CooldownParent = p
		end
		local cooldownframe = CreateFrame("Frame", framename, UIParent)
		cooldownframe:SetWidth(db.width)
		local height = db.height
		cooldownframe:SetHeight(height)

		local icon = cooldownframe:CreateTexture(framename.."Icon","BACKGROUND")
		icon:SetTexture(GetTexture(id, idtype))
		icon:SetWidth(height)
		icon:SetHeight(height)
		if not db.iconborder then
			icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
		end
		if db.mirrored then
			icon:SetPoint("TOPRIGHT")
		else
			icon:SetPoint("TOPLEFT")
		end
		cooldownframe.icon = icon

		local statustext = cooldownframe:CreateFontString(framename.."Text", 'OVERLAY')
		if db.mirrored then
			statustext:SetPoint('RIGHT', icon, 'LEFT')
		else
			statustext:SetPoint('RIGHT')
		end

		statustext:SetFontObject("CooldownWatchFont")
		statustext:SetJustifyH("RIGHT")
		cooldownframe.statustext = statustext

		if options.label then
			local text = options.label
			if type(text)~="string" then
				text = name
			end
			local label = cooldownframe:CreateFontString(framename.."Label", 'OVERLAY')
			if db.mirrored then
				label:SetPoint('LEFT')
			else
				label:SetPoint('LEFT', icon, 'RIGHT')
			end
			label:SetPoint('RIGHT', statustext, 'LEFT')
			label:SetFontObject("CooldownWatchFont")
			label:SetJustifyH("LEFT")
			label:SetText(text)
			cooldownframe.label = label
		end

		if not options.nobar then
			local tex = db.bartexture
			if db.mirrored then
				statusbar = CreateStatusBar(framename.."Bar", cooldownframe)
				statusbar:SetPoint('TOPLEFT')
				statusbar:SetPoint('BOTTOMRIGHT', icon, 'BOTTOMLEFT')
			else
				statusbar = CreateFrame("StatusBar", framename.."Bar", cooldownframe)
				statusbar:SetPoint('BOTTOMRIGHT')
				statusbar:SetPoint('TOPLEFT', icon, 'TOPRIGHT')
				statusbar:SetWidth(db.width - (height+1))
				statusbar:SetMinMaxValues(0,1)
				statusbar:SetValue(1)
			end
			statusbar:SetFrameLevel(cooldownframe:GetFrameLevel()-1)
			statusbar:SetStatusBarTexture(tex)
			if not statusbar:GetStatusBarTexture():GetTexture() then
				self:Print(format('|cffff2222Warning|r Status bar texture "%s" not found. Did you delete an addon? Reverting to default.',tex))
				tex = [[Interface\Addons\CooldownWatch\Smooth]]
				db.bartexture = tex
				statusbar:SetStatusBarTexture(tex)
				statusbar:Show()
			end
			statusbar:SetBackdrop{bgFile = tex}

			--[[
			if watchname=="Spott" or watchname=="Verblassen" then
				-- /run CooldownWatch.frame[5].statusbar:SetStatusBarColor(.4,.4,1,.4)
				statusbar:SetStatusBarColor(1,0,0,1)
				statusbar:SetBackdropColor(1,0,0,0.7)
			elseif watchname=="Donnerknall" then
				-- /run CooldownWatch.frame[10].statusbar:SetStatusBarColor(.4,.4,1,.4)
				statusbar:SetStatusBarColor(.4,.4,1,.4)
				statusbar:SetBackdropColor(unpack(db.backdropcolor))
			else
			--]]
				statusbar:SetStatusBarColor(unpack(db.statusbarcolor))
				statusbar:SetBackdropColor(unpack(db.backdropcolor))
			--end
			cooldownframe.statusbar = statusbar
		end

		cooldownframe.GetCooldown = GetCooldown
		cooldownframe.GetTexture = GetTexture
		cooldownframe.id = id
		cooldownframe.idtype = idtype
		cooldownframe.watchtype = watchtype
		cooldownframe.watchname = watchname
		cooldownframe.notfound = notfound
		cooldownframe.autohide = options.autohide
		cooldownframe.autohidebar = options.autohidebar

		frame[frameid] = cooldownframe
		cooldownframe.frameid = frameid
		cooldownframe.anchor = frameid==1 and CooldownWatchAnchor or frame[frameid-1]

		local layout = CooldownWatch.listlayout[CooldownWatchAnchor.layout]
		layout.Expand(cooldownframe)

		if watchtype == "spell" or watchtype == "petspell" then
			frameByName[name] = cooldownframe
		elseif watchtype == "equip" then
			tinsert(inventoryframe, cooldownframe)
			frameBySlot[idtype] = cooldownframe
		elseif watchtype == 'item' then
			tinsert(inventoryframe, cooldownframe)
		elseif watchtype == "debug" then
			cooldownframe.duration = options.duration
			cooldownframe.remaining = options.remaining
		end

		if notfound then
			layout.Collapse(cooldownframe)
			cooldownframe:Hide()
		end

		return cooldownframe
	end

	function CooldownWatch:CheckTalents()
		local layout = self.listlayout[CooldownWatchAnchor.layout]
		for frameid, cooldownframe in ipairs(frame) do
			-- Bugfix: Only check spells, not items/equip slots.
			local isSpell = cooldownframe.watchtype == "spell" or cooldownframe.watchtype == "petspell"
			local changed = isSpell and (not not cooldownframe.notfound)~=(not GetSpellIndex(cooldownframe.watchname, cooldownframe.watchtype == "spell" and BOOKTYPE_SPELL or BOOKTYPE_PET))
			if changed then
				if cooldownframe.notfound then
					self:Trace("learned",cooldownframe.watchname)
					layout.Expand(cooldownframe)
					cooldownframe:Show()
					-- Bugfix: Don't call :ConfigMode(), because it loads CWConfig.
					if self.configmode then
						if not cooldownframe.autohide then
							cooldownframe:SetAlpha(1)
						end
					else
						-- Bugfix: Auto-hide frames should be initialized properly.
						cooldownframe.Stop()
						cooldownframe.CheckCooldown()
					end
					-- Set icon
					cooldownframe.icon:SetTexture(cooldownframe.GetTexture(cooldownframe.id, cooldownframe.idtype))
					cooldownframe.notfound = nil
				else
					self:Trace("unlearned",cooldownframe.watchname)
					if not self.configmode then
						cooldownframe:Hide()
						layout.Collapse(cooldownframe)
					end
					cooldownframe.notfound = true
				end
			end
		end
	end



	local function CreateOnUpdate(cooldownframe, options)
		if not cooldownframe then return end
		local f = CooldownWatch:GetOnUpdate(options or {})
		if not env then
			env = {
				seconds = seconds,
				minutes = minutes,
				GetTime = GetTime,
				floor = floor,
				frame = frame,
				CooldownWatchAnchor = CooldownWatchAnchor,
				CooldownWatch = CooldownWatch,
			}
		end
		-- create string, compile and execute it, and return the created function
		local OnUpdate = f.using(env)
		return OnUpdate and OnUpdate(cooldownframe, CooldownWatch.listlayout[CooldownWatchAnchor.layout])
	end

	function CooldownWatch:AddWatch(watchtype, watchname, options)
		local cooldownframe = self:CreateWatchFrame(watchtype, watchname, options)
		CreateOnUpdate(cooldownframe, options)
		return cooldownframe
	end

	function CooldownWatch:GetOnUpdate(options)
		local MINDURATION = 1.6
		local qqlib = LibStub:GetLibrary('qqlib-1.0')
		local Function = qqlib.Function
		local func = Function('cooldownframe, layout')
		local f = func.code
		local debug = CooldownWatch:IsDebugEnabled()
		local trace = debug and debug >= 3
		local autohide = options.autohide
		local bar = not options.nobar
		local autohidebar = bar and not autohide and options.autohidebar

		f('local CheckCooldown, Start, Stop, OnUpdate')
		f('local Expand, Collapse',autohide)
		f('local remaining, nextupdate')
		f('local nextupdatebar, stepbar',bar)
		f('local cdstart,cdduration')

		f('local id = cooldownframe.id')
		f('local idtype = cooldownframe.idtype')
		f('local GetCooldown_stupid = cooldownframe.GetCooldown')
		f('local function GetCooldown()')
		f('	local start, duration, enable = GetCooldown_stupid(id, idtype)')
		-- Bugfix: If the computer was started after the cooldown, start should be a negative number. 
		-- WoW, however, reports it as a large positive number (offset by 2^32/1000=4294967.296, because 2^32-1 is the maximum for GetTickCount).
		-- Let's say the cooldown was started 100s before the CPU. 
		-- Then start would be reported as 4294967.296-100 = 4294867.296 instead of -100.
		-- Normally, start+duration-GetTime() is the time left.
		-- This number must not be greater than duration.
		-- start+duration-GetTime() <= duration  
		-- <==> start <= GetTime()
		f('	if start and start > GetTime() then')
		f('		start = start-4294967.296')
		f('	end')
		f('	return start, duration, enable')
		f('end')

		f('local statusbar = cooldownframe.statusbar', bar)
		f('local statustext = cooldownframe.statustext')

		f('local statusBar_SetValue = statusbar.SetValue', bar)
		f('local statustext_SetText = statustext.SetText')
		f('local statustext_SetFormattedText = statustext.SetFormattedText')

		f('CooldownWatch:Trace("autohide")', trace and autohide)

		f('local left')
		f('local SetNextSecond, SetNextMinute, SetNextHour')
		f('local UpdateText') -- one of the above SetNext* functions

		-- CheckCooldown is also used as the OnEvent handler.
		f('function CheckCooldown()')
		f('	local start, duration, enable = GetCooldown()')
		f('	CooldownWatch:Trace("CheckCooldown", id, start, duration, enable, remaining)',trace)
		f('	if start then')
		f('		if enable == 0 then')
		f('			cooldownframe:RegisterEvent("SPELL_UPDATE_COOLDOWN")') -- TODO Only necessary for spells?
		f('		elseif duration < '..MINDURATION..' then')
		f('			if remaining and (duration == 0 or start+duration < cdstart+cdduration) then')
		-- The spell is either off cooldown now, or on a GCD that ends before the original cooldown.
		f('				Stop()')
		f('			end')
		f('		elseif enable == 1 and duration >= '..MINDURATION..' then')-- stupid GCD =/
		f('			if duration~=cdduration or start~=cdstart then')
		f('				cdstart, cdduration = start, duration')
		f('				return Start(duration)')
		f('			end')
		f('		end')
		f('	end')
		f('	return remaining')
		f('end')

		f('function Start(duration)')
		f('	cooldownframe:RegisterEvent("SPELL_UPDATE_COOLDOWN")') -- TODO Only necessary for spells?
		f('	UpdateText = SetNextHour') -- see below
		f('	statusbar:SetMinMaxValues(0,duration)', bar)
		f('	nextupdatebar = duration', bar)
		f('	stepbar = duration/(statusbar:GetWidth()*statusbar:GetEffectiveScale())*3/4', bar) -- 3/4 probably because of the pixel aspect ratio
		f('	remaining = duration+GetCooldown()-GetTime()')
		f('	left = floor(remaining/3600)+1')
		f('	nextupdate = left*3600')
		f('	left = left+1')
		f('	cooldownframe:SetScript("OnUpdate", OnUpdate)')
		f('	cooldownframe:Show()', autohide)
		f('	CooldownWatch:Trace("Start",id,duration,remaining,stepbar)',trace)
		f('	statusbar:Show()',autohidebar)
		f('	if remaining>=nextupdate then')
		f('		statustext_SetText(statustext, "too long")')
		f('	end')
		f('	return remaining')
		f('end')

		f('function OnUpdate(_, elapsed)')
		f('	CooldownWatch:Debug(4,"OnUpdate", elapsed, GetTime())',trace)
		f('	local rem = remaining - elapsed')
		f('	remaining = rem')
		if bar then
		f('	if rem<nextupdatebar then')
		f('		nextupdatebar = floor(rem/stepbar)*stepbar')
		f('		statusBar_SetValue(statusbar, rem)')
		f('		CooldownWatch:Debug(4,"nextupdatebar ",id,rem,nextupdatebar)',trace)
		f('	end')
		end
		f('	if rem<nextupdate then')
					-- we need to update, so goto the current update code
		f('		UpdateText()')
		f('	end')
		f('end')

		-- more than 120m cooldown
		f('function SetNextHour()')
		f('	if remaining>=7200 then')
					-- still more than 2h cd, determine the next update
		--f('		local left = floor(remaining/3600)')
		--f('		nextupdate = left*3600')
					-- Using a loop here is usually faster than computing left and nextupdate explicitely
					-- (~25 times if it's only one iteration, which should be always the case). Since we're using
					-- a repeat loop, we just have to make sure that remaining<nextupdate is true before
					-- calling SetNextMinute().
		f('		repeat')
		f('			left = left-1')
		f('			nextupdate = nextupdate-3600')
		f('		until remaining>=nextupdate')
		f('		statustext_SetFormattedText(statustext, "%dh", left)')
		f('	else')
		f('		left = floor(remaining/60)+1')
		f('		nextupdate = left*60')
		f('		left = left+1')
		f('		UpdateText = SetNextMinute')
		f('		UpdateText()')
		f('	end')
		f('end')

		-- less than 120m cooldown
		f('function SetNextMinute()')
		f('	if remaining>=120 then')
					-- still more than 120s cd, determine the next update
		f('		repeat')
		f('			left = left-1')
		f('			nextupdate = nextupdate-60')
		f('		until remaining>=nextupdate')
		f('		statustext_SetText(statustext, minutes[left])')
		f('	else')
		f('		nextupdate = floor(remaining)+1')
		f('		UpdateText = SetNextSecond')
		f('		UpdateText()')
		f('	end')
		f('end')

		-- less than 120s cooldown
		f('function SetNextSecond()')
		f('	if remaining>=0 then')
					-- still on cd, determine the next update
		f('		local left')
		f('		repeat')
		f('			left = nextupdate')
		f('			nextupdate = nextupdate-1')
		f('		until remaining>=nextupdate')
		f('		statustext_SetText(statustext, seconds[left])')
		f('	else')
		f('		Stop()')
		f('	end')
		f('end')

		f('function Stop()')
		f('	CooldownWatch:Trace("Stop",id)',trace)
		f('	cooldownframe:SetScript("OnUpdate", nil)')
		f('	statusbar:SetValue(0)', bar)
		f('	statustext:SetText("")')
		f('	cooldownframe:UnregisterEvent("SPELL_UPDATE_COOLDOWN")')
		f('	cooldownframe:Hide()', autohide)
		f('	statusbar:Hide()', autohidebar)
		f('	cdstart, cdduration = 0,0')
		f('	remaining=nil')
		f('end')

		--[[
		Autohide frames: Show=Expand, Hide=Collapse
		What we need to do for frames is: When the UIParent is hidden, Stop everything, when it's shown, CheckCooldown.
		--]]
		f('Collapse, Expand = layout.Collapse, layout.Expand', autohide)
		f('cooldownframe:SetScript("OnHide", Collapse)', autohide)
		f('cooldownframe:SetScript("OnShow", Expand)', autohide)
		f('cooldownframe:SetScript("OnEvent", CheckCooldown)')
		f('Stop()')
		f('CheckCooldown()')

		f('cooldownframe.CheckCooldown = CheckCooldown')
		f('cooldownframe.Stop = Stop')
		return func
	end
end
end

----------------------------------------------------------------------------------------------
if CooldownWatch.modules.Config.status=='load' then
function CooldownWatch.modules.Config.init(...)
	-- initialize local variables/upvalues
	local frame, frameByName, inventoryframe, frameBySlot, seconds, minutes = ...
	local db = CooldownWatchVar

	local function SetAllAnchors()
		local anchor = CooldownWatchAnchor
		for frameid, cooldownframe in ipairs(frame) do
			cooldownframe.frameid = frameid
			cooldownframe.anchor = anchor
			anchor = cooldownframe
		end
	end

	function CooldownWatch:MoveFrame(frameid, newid)
		self:Trace("MoveFrame", frameid, newid)
		if frameid == newid then return end
		local f = tremove(frame, frameid)
		tinsert(frame, newid, f)
		SetAllAnchors()
		self:SetAllPoints()
	end

	function CooldownWatch:RemoveWatch(frameid)
		self:Trace("RemoveWatch", frameid)
		local f = tremove(frame, frameid)
		f.Stop()
		--tremove(db.frameData, frameid)
		f:Hide()
		SetAllAnchors()
		self:SetAllPoints()
		if f.watchtype == "spell" or f.watchtype == "petspell" then
			frameByName[f.watchname] = nil
		end
	end

	function CooldownWatch:RecreateAll()
		for i=1,#frame do
			local f = frame[i]
			local options = {
				nobar = f.statusbar == nil,
				label = f.label and f.label:GetText(),
				autohide = f.autohide,
				autohidebar = f.autohidebar,
				duration = f.duration,
				remaining = f.remaining,
			}

			CooldownWatch:RemoveWatch(i)
			local newframe = CooldownWatch:AddWatch(f.watchtype, f.watchname, options)
			CooldownWatch:MoveFrame(newframe.frameid, f.frameid)
		end
	end

	local configmode = false
	local dragged = nil

	local StartConfiguring, StopConfiguring, getopts
	
	local function OnUpdate(f, elapsed)
		if not IsMouseButtonDown('LeftButton') then
			dragged = nil
			CooldownWatchAnchor:SetScript('OnUpdate', nil)
		end
	end
	local function OnDragStart(f)
		CooldownWatchAnchor:SetScript('OnUpdate', OnUpdate)
		dragged = f.frameid
	end

	local function OnEnter(f)
		if dragged then
			local draggedto = f.frameid
			if dragged == draggedto then return end
			CooldownWatch:MoveFrame(dragged, draggedto)
			dragged = draggedto
		end
	end

	local function OnReceiveDrag(f)
		local linktype, info1, info2 = GetCursorInfo()
		--CooldownWatch:Print(linktype or 'nil', info1 or 'nil', info2 or 'nil')

		local watchtype, watchname, options
		if linktype=='spell' then
			watchtype, watchname = 'spell', (GetSpellName(info1, BOOKTYPE_SPELL))
		elseif linktype=='item' then
			-- check if the item was picked up from an inventory slot
			local equipslot
			for i=0,19 do 
				if IsInventoryItemLocked(i) then 
					equipslot=i
					break
				end 
			end
			if equipslot then
				watchtype, watchname = 'equip', equipslot
			else
				watchtype, watchname = 'item', info1
			end
		end
		if not watchtype then return end
		if f.frameid then
			options = getopts(f)
		end

		local newframe = CooldownWatch:AddWatch(watchtype, watchname, options)
		if newframe then
			StartConfiguring(newframe)
			if f.frameid then
				CooldownWatch:MoveFrame(newframe.frameid, f.frameid)
			end
		end
		ClearCursor()
	end

	local dewdrop = AceLibrary("Dewdrop-2.0")
	local L = setmetatable({}, {__index=function(t,k) return k end})
	local currentoptions, currentframe
	local function recreate(f, options)
		CooldownWatch:RemoveWatch(f.frameid)
		currentframe = CooldownWatch:AddWatch(f.watchtype, f.watchname, options)
		CooldownWatch:MoveFrame(currentframe.frameid, f.frameid)
		StartConfiguring(currentframe)
	end

	local options = {
		type = "group",
		args = {
			header = {
				type = 'header',
			},
			nobar = {
				type = "toggle",
				name = L["Bar"],
				desc = L["Toggle bar."],
				get = function() 
					return not currentoptions.nobar
				end,
				set = function(v) 
					currentoptions.nobar = not v
					recreate(currentframe, currentoptions)
				end,
				order = 200,
			},
			label = {
				type = "text",
				name = L["Label"],
				desc = L["Change the label. Don't forget to press Enter."],
				usage = '',
				get = function() 
					return currentoptions.label or ""
				end,
				set = function(v) 
					currentoptions.label = v=="" and nil or v
					recreate(currentframe, currentoptions)
				end,
				order = 300,
			},
			defaultlabel = {
				type = "execute",
				name = "Label by name",
				desc = "Label by name.",
				hidden = function() return not CooldownWatch:GetCooldownName(currentframe) end,
				func = function()
					currentoptions.label = CooldownWatch:GetCooldownName(currentframe)
					recreate(currentframe, currentoptions)
				end,
				order = 400,
			},
			alwaysshow = {
				type = "toggle",
				name = L["Always show"],
				desc = L["Auto-hiding disabled."],
				isRadio = true,
				get = function() 
					return not currentoptions.autohide and not currentoptions.autohidebar
				end,
				set = function(v) 
					currentoptions.autohide = false
					currentoptions.autohidebar = false
					recreate(currentframe, currentoptions)
				end,
				order = 500,
			},
			autohide = {
				type = "toggle",
				name = L["Auto-hide frame"],
				desc = L["Cooldown frame hidden if not on cooldown."],
				isRadio = true,
				get = function() 
					return currentoptions.autohide
				end,
				set = function(v) 
					currentoptions.autohide = v
					currentoptions.autohidebar = false
					recreate(currentframe, currentoptions)
				end,
				order = 600,
			},
			autohidebar = {
				type = "toggle",
				name = L["Auto-hide bar"],
				desc = L["Cooldown bar hidden if not on cooldown."],
				isRadio = true,
				get = function() 
					return currentoptions.autohidebar
				end,
				set = function(v) 
					currentoptions.autohide = false
					currentoptions.autohidebar = v
					recreate(currentframe, currentoptions)
				end,
				order = 700,
			},
			delete = {
				type = "execute",
				name = L["Delete"],
				desc = L["Deletes this cooldown bar. No Undo."],
				func = function()
					CooldownWatch:RemoveWatch(currentframe.frameid)
					dewdrop:Close()
				end,
				order = 800,
			},
		}
	}
	function getopts(cooldownframe)
		local options = {
			nobar = cooldownframe.statusbar == nil or false,
			label = cooldownframe.label and cooldownframe.label:GetText(),
			autohide = cooldownframe.autohide,
			autohidebar = cooldownframe.autohidebar,
			duration = cooldownframe.duration,
			remaining = cooldownframe.remaining,
		}
		return options
	end

	local recreateAll = false
	local globaloptions = {
		type = "group",
		args = {
			height = {
				type = "range",
				name = L["Height"],
				desc = L["Height"],
				min = 8,
				max = 30,
				step = 1,
				get = function() 
					return db.height
				end,
				set = function(v)
					db.height = v
					for i,f in ipairs(frame) do
						f:SetHeight(v)
						if f.icon then
							f.icon:SetWidth(v)
							f.icon:SetHeight(v)
						end
					end
				end,
				order = 300,
			},
			width = {
				type = "range",
				name = L["Width"],
				desc = L["Width"],
				min = 40,
				max = 500,
				step = 1,
				get = function() 
					return db.width
				end,
				set = function(v)
					if v==db.width then
						return
					end 
					db.width = v
					for i,f in ipairs(frame) do
						f:SetWidth(v)
					end
				end,
				order = 300,
			},
			spacing = {
				type = "range",
				name = L["Spacing"],
				desc = L["Spacing"],
				min = 0,
				max = 10,
				step = 1,
				get = function() 
					return db.spacing
				end,
				set = function(v)
					CooldownWatch:SetSpacing(v)
					local layout = CooldownWatch.listlayout[CooldownWatchAnchor.layout]
					for i,f in ipairs(frame) do
						layout.Expand(f)
					end
					recreateAll=true
				end,
				order = 350,
			},
			statusbarcolor = {
				type = "color",
				name = L["Bar color"],
				desc = L["Bar color"],
				hasAlpha = true,
				get = function()
					return unpack(db.statusbarcolor)
				end,
				set = function(r,g,b,a)
					for i,f in ipairs(frame) do
						if f.statusbar then
							f.statusbar:SetStatusBarColor(r,g,b,a)
						end
					end
					db.statusbarcolor = {r,g,b,a}
				end,
				order = 400,
			},
			backdropcolor = {
				type = "color",
				name = L["Bar background color"],
				desc = L["Bar background color"],
				hasAlpha = true,
				get = function()
					return unpack(db.backdropcolor)
				end,
				set = function(r,g,b,a)
					for i,f in ipairs(frame) do
						if f.statusbar then
							f.statusbar:SetBackdropColor(r,g,b,a)
						end
					end
					db.backdropcolor = {r,g,b,a}
				end,
				order = 401,
			},
			fontsize = {
				type = "range",
				name = L["Text size"],
				desc = L["Text size"],
				min = 5,
				max = 21,
				step = 1,
				get = function() 
					return db.fontsize
				end,
				set = function(v)
					if v==db.fontsize then
						return
					end 
					db.fontsize = v
					CooldownWatchFont:SetFont(db.font, db.fontsize, 'OUTLINE')
				end,
				order = 401.5,
			},
			textcolor = {
				type = "color",
				name = L["Text color"],
				desc = L["Text color"],
				hasAlpha = true,
				get = function()
					return unpack(db.textcolor)
				end,
				set = function(r,g,b,a)
					CooldownWatchFont:SetTextColor(r,g,b,a)
					db.textcolor = {r,g,b,a}
				end,
				order = 402,
			},
			textshadowcolor = {
				type = "color",
				name = L["Text shadow color"],
				desc = L["Text shadow color"],
				hasAlpha = true,
				hidden = true, -- why is this not working?
				get = function()
					return unpack(db.textshadowcolor)
				end,
				set = function(r,g,b,a)
					for i,f in ipairs(frame) do
						if f.label then
							f.label:SetShadowColor(r,g,b,a)
						end
						if f.statustext then
							f.statustext:SetShadowColor(r,g,b,a)
						end
					end
					db.textshadowcolor = {r,g,b,a}
				end,
				order = 403,
			},
			mirrored = {
				type = "toggle",
				name = L["Mirrored"],
				desc = L["Icon on the right side, bars move from left to right. (No preview, exit config mode)"],
				get = function() 
					return db.mirrored
				end,
				set = function(v) 
					db.mirrored = v
					recreateAll=true
				end,
				order = 700,
			},
			iconborder = {
				type = "toggle",
				name = L["Icon border"],
				desc = L["Toggles the default Blizzard icon border."],
				get = function() 
					return db.iconborder
				end,
				set = function(v) 
					db.iconborder = v
					for i,f in ipairs(frame) do
						if v and f.icon then
							f.icon:SetTexCoord(0,1,0,1)
						else
							f.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
						end
					end
				end,
				order = 701,
			},
			layout = {
				type = "group",
				name = L["List layout"],
				desc = L["List layout"],
				pass = true,
				order = 500,
				get = function(v)
					return db.layout == v
				end,
				set = function(v)
					db.layout = v 
					CooldownWatchAnchor.layout = v
					local layout = CooldownWatch.listlayout[v]
					for i,f in ipairs(frame) do
						layout.Expand(f)
					end
					recreateAll=true
				end,
				args = {
					TOP_BOTTOM = {type='toggle', name = 'Top to bottom', desc = 'Top to bottom', passValue='TOP_BOTTOM', isRadio=true, order=1 },
					BOTTOM_TOP = {type='toggle', name = 'Bottom to top', desc = 'Bottom to top', passValue='BOTTOM_TOP', isRadio=true, order=2 },
					LEFT_RIGHT = {type='toggle', name = 'Left to right', desc = 'Left to right', passValue='LEFT_RIGHT', isRadio=true, order=3 },
					RIGHT_LEFT = {type='toggle', name = 'Right to left', desc = 'Right to left', passValue='RIGHT_LEFT', isRadio=true, order=4 },
				},
			},
			exit = {
				type = "execute",
				name = L["Exit config mode"],
				desc = L["You have to exit config mode for cooldowns to work. Enter /cooldownwatch or /cw to toggle config mode."],
				func = function() dewdrop:Close() CooldownWatch:ConfigMode(false) end,
				order = -1,
			},
		},
	}
	local SML
	if LibStub and LibStub("LibSharedMedia-3.0", 'SILENCE! I KILL YOU!') then
		SML = LibStub("LibSharedMedia-3.0")
	elseif AceLibrary and AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary:HasInstance("SharedMedia-1.0") then
		SML = AceLibrary("SharedMedia-1.0")
	end
	local current = db.bartexture
	local tooltip = "Bar texture"
	if not SML then
		tooltip = "This feature requires SharedMediaLib."
	end
	globaloptions.args.bartexture = {
		type = "group",
		name = L["Bar texture"],
		desc = L[tooltip],
		pass = true,
		args = {
			current = {type='toggle', name = 'current', desc = 'default', passValue=current, isRadio=true, order=1, hidden = function() return [[Interface\Addons\CooldownWatch\Smooth]] == current end, },
			default = {type='toggle', name = 'default', desc = 'default', passValue=[[Interface\Addons\CooldownWatch\Smooth]], isRadio=true, order=2 },
		},
		get = function(v)
			return db.bartexture == v
		end,
		set = function(v)
			db.bartexture = v
			for i,f in ipairs(frame) do
				if f.statusbar then
					f.statusbar:SetStatusBarTexture(db.bartexture)
					local backdrop = f.statusbar:GetBackdrop()
					backdrop.bgFile = db.bartexture
					f.statusbar:SetBackdrop(backdrop)
					f.statusbar:SetBackdropColor(unpack(db.backdropcolor))
				end
			end
		end,
		order = -3,
	}
	local tooltip = "Font"
	if not SML then
		tooltip = "This feature requires SharedMediaLib."
	end
	local current = db.font
	globaloptions.args.font = {
		type = "group",
		name = L["Font"],
		desc = L[tooltip],
		pass = true,
		args = {
			default = {type='toggle', name = 'default', desc = 'default', passValue="Fonts\\FRIZQT__.TTF", isRadio=true, order=2 },
			current = {
				type='toggle', name = 'current', desc = 'default', 
				passValue=current, isRadio=true, order=1, hidden = function() return "Fonts\\FRIZQT__.TTF" == current end, 
			},
		},
		get = function(path)
			return path:lower()==db.font:lower()
		end,
		set = function(path)
			db.font = path
			CooldownWatchFont:SetFont(path, db.fontsize, 'OUTLINE')
		end,
		order = -2,
	}
	if SML then
		-- status bar textures
		local args = globaloptions.args.bartexture.args
		local order = 2
		for name,path in pairs(SML:HashTable('statusbar')) do
			order = order + 1
			args[name] = {type='toggle', name = name, desc = name, passValue=path, isRadio=true, order = order }
		end
		-- fonts
		local args = globaloptions.args.font.args
		local order = 2
		for name,path in pairs(SML:HashTable('font')) do
			order = order + 1
			args[name] = {type='toggle', name = name, desc = name, passValue=path, isRadio=true, order = order }
		end
	end

	local slots = {
		"HeadSlot",
		"NeckSlot",
		"ShoulderSlot",
		"BackSlot",
		"ChestSlot",
		"ShirtSlot",
		"TabardSlot",
		"WristSlot",
		"HandsSlot",
		"WaistSlot",
		"LegsSlot",
		"FeetSlot",
		"Finger0Slot",
		"Finger1Slot",
		"Trinket0Slot",
		"Trinket1Slot",
		"MainHandSlot",
		"SecondaryHandSlot",
		"RangedSlot",
		"AmmoSlot",
	}
	local slotname = {}
	for i,slot in pairs(slots) do
		local k = GetInventorySlotInfo(slot)
		local name = getglobal(slot:upper())
		local number = slot:match('[0-9]')
		if number then
			name = name..' '..(tonumber(number)+1)
		end
		slotname[k] = name
	end

	function CooldownWatch:GetCooldownName(f)
		local watchtype = f.watchtype
		if watchtype == 'spell' then
			return f.watchname
		elseif watchtype == 'equip' then
			return slotname[f.idtype]
		elseif watchtype == 'item' then
			return (GetItemInfo(f.id))
		end
	end

	local function OnMouseUp(f, button)
		if button == 'LeftButton' and CursorHasItem() or CursorHasSpell() then
			OnReceiveDrag(f)
		elseif button == 'MiddleButton' and f.frameid then
			CooldownWatch:RemoveWatch(f.frameid)
		elseif button == 'RightButton' then
			if f.frameid then
				currentframe = f
				currentoptions = getopts(f)
				dewdrop:Open(f, 'children', function()
					local header = options.args.header
					local name = CooldownWatch:GetCooldownName(f)
					if name then
						header.name = name
						header.hidden = false
					else
						header.hidden = true
						header.name = ' '
					end
					dewdrop:FeedAceOptionsTable(options) 
				end)
			else
				dewdrop:Open(f, 'children', function()
					dewdrop:FeedAceOptionsTable(globaloptions)
				end)
			end
		end
	end

	function CooldownWatch:SetPoints(cooldownframe)
		cooldownframe:ClearAllPoints()
		local frameid = cooldownframe.frameid
		local collapse = cooldownframe.notfound or (cooldownframe.autohide and not cooldownframe:IsShown())
		if CooldownWatchAnchor:IsShown() then
			collapse = false
		end
		local layout = CooldownWatch.listlayout[CooldownWatchAnchor.layout]
		if collapse then
			layout.Collapse(cooldownframe)
		else
			layout.Expand(cooldownframe)
		end
	end

	function CooldownWatch:SetAllPoints()
		for frameid, cooldownframe in ipairs(frame) do
			CooldownWatch:SetPoints(cooldownframe)
		end
	end

	function StartConfiguring(f)
		f.Stop()
		if f.autohide then
			f:Show()
			f:SetAlpha(0.3)
		elseif f.notfound then
			local layout = CooldownWatch.listlayout[CooldownWatchAnchor.layout]
			layout.Expand(f)
			f:Show()
			f:SetAlpha(0.3)
		end
		if f.statusbar then
			f.statusbar:SetMinMaxValues(0,1)
			f.statusbar:SetValue(0.5)
		end
		if f.statustext then
			f.statustext:SetText('15s')
		end
		f:EnableMouse(true)
		f:RegisterForDrag("LeftButton")
		f:SetScript('OnMouseUp', OnMouseUp)
		f:SetScript('OnDragStart', OnDragStart)
		f:SetScript('OnReceiveDrag', OnReceiveDrag)
		f:SetScript('OnEnter', OnEnter)
	end

	function StopConfiguring(f)
		if f.autohide then
			f:SetAlpha(1)
		elseif f.notfound then
			f:SetAlpha(1)
			local layout = CooldownWatch.listlayout[CooldownWatchAnchor.layout]
			layout.Collapse(f)
			f:Hide()
		end
		f.Stop()
		f.CheckCooldown()
		f:RegisterForDrag()
		f:EnableMouse(false)
		f:SetScript('OnMouseUp', nil)
		f:SetScript('OnDragStart', nil)
		f:SetScript('OnReceiveDrag', nil)
		f:SetScript('OnEnter', nil)
	end

	function CooldownWatch:GetConfigMode()
		return configmode
	end
	function CooldownWatch:ToggleConfigMode()
		self:ConfigMode(not configmode)
	end

	local function SaveFrameData()
		local frameData = {}
		db.frameData = frameData
		for i,cooldownframe in ipairs(frame) do 
			local options = {
				nobar = cooldownframe.statusbar == nil or nil,
				label = cooldownframe.label and cooldownframe.label:GetText(),
				autohide = cooldownframe.autohide,
				autohidebar = cooldownframe.autohidebar,
				duration = cooldownframe.duration,
				remaining = cooldownframe.remaining,
			}
			table.insert(frameData, {
				watchtype = cooldownframe.watchtype,
				watchname = cooldownframe.watchname,
				options = options,
			})
		end
	end

	local hint = 'config mode enabled, enter /cw to disable it'
	function CooldownWatch:ConfigMode(enable)
		enable = not not enable
		if enable == configmode then
			return 
		end
		configmode = enable
		self.configmode = configmode
		if enable then
			self:Print(hint)
			hint = 'config mode enabled'
			CooldownWatch:StopEventListening()
			CooldownWatchAnchor:Show()
			CooldownWatchAnchor:SetScript('OnReceiveDrag',OnReceiveDrag)
			CooldownWatchAnchor:SetScript('OnMouseUp', OnMouseUp)
			for frameid, f in ipairs(frame) do
				StartConfiguring(f)
			end
			recreateAll = false
		else
			self:Print('config mode disabled')
			CooldownWatchAnchor:Hide()
			CooldownWatchAnchor:SetScript('OnReceiveDrag',nil)
			CooldownWatchAnchor:SetScript('OnMouseUp', nil)
			for frameid, f in ipairs(frame) do
				StopConfiguring(f)
			end
			CooldownWatch:StartEventListening()
			if recreateAll then
				self:Debug(1, 'RecreateAll')
				CooldownWatch:RecreateAll()
			end
			CooldownWatch:SetAllPoints()
			SaveFrameData()
		end
	end
end
end
